using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Net;

using GrapplingHookGameData;

namespace GrapplingHookGame
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class Camera2D : Microsoft.Xna.Framework.GameComponent
    {
        #region Members

        private Vector2 position;
        private Vector2 origin;
        private float rotation;
        private float scale; //zoom
        private float speed;

        private Matrix transform;
        private GraphicsDevice graphicsDevice;
        bool manualcamera;

        #endregion

        #region Properties

        public Matrix Transform
        {
            get { return transform; }
            set { transform = value; }
        }

        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }

        public Vector2 Origin
        {
            get { return origin; }
            set { origin = value; }
        }

        public float Rotation
        {
            get { return rotation; }
            set { rotation = value; }
        }

        public float Scale
        {
            get { return scale; }
            set { scale = value; }
        }

        public float Speed
        {
            get { return speed; }
            set { speed = value; }
        }

        public bool ManualCamera
        {
            get { return manualcamera; }
            set { manualcamera = value; }
        }

        #endregion

        #region Methods

        public Camera2D(Game game, bool manualcamera)
            : base(game)
        {
            graphicsDevice = game.GraphicsDevice;
            this.manualcamera = manualcamera;
            position = Vector2.Zero;
            origin = Vector2.Zero;
            rotation = 0;
            scale = 1;
            speed = 5;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            //Our start position, rotation, scale, and translation speed.
            //These are all changeable by property but I'm sure a function would work just as well/better,
            //feel free to experiment.
            base.Initialize();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (manualcamera)
            {
                //translation controls, left stick xbox or WASD keyboard
                if (Keyboard.GetState().IsKeyDown(Keys.A)
                    || GamePad.GetState(PlayerIndex.One).DPad.Left == ButtonState.Pressed
                    || GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X < 0) { position.X -= speed; }
                if (Keyboard.GetState().IsKeyDown(Keys.D)
                    || GamePad.GetState(PlayerIndex.One).DPad.Right == ButtonState.Pressed
                    || GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X > 0) { position.X += speed; }
                if (Keyboard.GetState().IsKeyDown(Keys.S)
                    || GamePad.GetState(PlayerIndex.One).DPad.Down== ButtonState.Pressed
                    || GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.Y < 0) { position.Y += speed; }
                if (Keyboard.GetState().IsKeyDown(Keys.W)
                    || GamePad.GetState(PlayerIndex.One).DPad.Up == ButtonState.Pressed
                    || GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.Y > 0) { position.Y -= speed; }

                //rotation controls, right stick or QE keyboard
                if (Keyboard.GetState().IsKeyDown(Keys.Q)
                    || GamePad.GetState(PlayerIndex.One).ThumbSticks.Right.X < 0) { rotation += 0.01f; }
                if (Keyboard.GetState().IsKeyDown(Keys.E)
                    || GamePad.GetState(PlayerIndex.One).ThumbSticks.Right.X > 0) { rotation -= 0.01f; }

                //zoom/scale controls, left/right triggers or CZ keyboard
                if (Keyboard.GetState().IsKeyDown(Keys.C)
                    || GamePad.GetState(PlayerIndex.One).Triggers.Right > 0) { scale += 0.001f; }
                if (Keyboard.GetState().IsKeyDown(Keys.Z)
                    || GamePad.GetState(PlayerIndex.One).Triggers.Left > 0) { scale -= 0.001f; }
            }
            //
            transform =                
                          Matrix.CreateTranslation(new Vector3(-position.X,-position.Y, 0))
                * Matrix.CreateScale(new Vector3((scale), (scale), 0))
                          * Matrix.CreateTranslation(new Vector3(origin.X, origin.Y, 0))
                * Matrix.CreateRotationZ(rotation)
/*                * Matrix.CreateTranslation(new Vector3(
                                            (graphicsDevice.Viewport.Width / 2) + position.X,
                                            (graphicsDevice.Viewport.Height / 2) + position.Y, 0)
  */
            
            ;
            
            base.Update(gameTime);
        }

        public void MoveAndZoomToIncludeLocations(List<Vector2> locations)
        {
            //if only one location to zoom on put it in the middle
            if (locations.Count <= 1)
            {
                position = new Vector2( locations[0].X, locations[0].Y);
                scale = 1.0f;
            }
            //zoom to include all locations
            else
            {
                int minX = int.MaxValue;
                int minY = int.MaxValue;
                int maxX = int.MinValue;
                int maxY = int.MinValue;

                //find min bounding rectangle for locations
                foreach (Vector2 vec in locations)
                {
                    maxX = (int)Math.Max(maxX, vec.X);
                    maxY = (int)Math.Max(maxY, vec.Y);
                    minX = (int)Math.Min(minX, vec.X);
                    minY = (int)Math.Min(minY, vec.Y);
                }

                //add a border to players are not at edge of screen
                minX -= Constants.ZOOM_BORDER;
                minY -= Constants.ZOOM_BORDER;
                maxX += Constants.ZOOM_BORDER;
                maxY += Constants.ZOOM_BORDER;

                //position camera in middle of minimum bounding rectangle
                position = new Vector2((maxX - minX) / 2 + minX, (maxY - minY) / 2 + minY);

                //determine if we should zoom. only zoom out if players are further apart than the screen width
                int locationXDelta = Math.Max(graphicsDevice.Viewport.Width, maxX - minX);
                int locationYDelta = Math.Max(graphicsDevice.Viewport.Height, maxY - minY);

                float widthRel = (float)graphicsDevice.Viewport.Width / locationXDelta;
                float heightRel = (float)graphicsDevice.Viewport.Height / locationYDelta;

                scale = Math.Min(widthRel, heightRel);

            }
        }

        public void EnsureIsWithinRectangle(Rectangle bounds)
        {
            //calculate displayed size for zoom
            float zoomedWidth = graphicsDevice.Viewport.Width / scale;
            float zoomedHeight = graphicsDevice.Viewport.Height / scale;

            //check left edge
            float leftEdge = (position.X - zoomedWidth / 2);
            if( leftEdge < bounds.X ) position.X += (bounds.X-leftEdge);

            //check right edge
            float rightEdge = (position.X + zoomedWidth / 2);
            if (rightEdge > bounds.Right) position.X -= (rightEdge - bounds.Right);

            //check top edge
            float topEdge = (position.Y - zoomedHeight/ 2);
            if (topEdge < bounds.Y) position.Y += (bounds.Y - topEdge);

            //check bottom edge
            float bottomEdge = (position.Y + zoomedHeight/ 2);
            if (bottomEdge > bounds.Bottom) position.Y -= (bottomEdge - bounds.Bottom);


        }

        #endregion
    }
}